/*
 *  linux/kernel/fork.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  'fork.c' contains the help-routines for the 'fork' system call
 * (see also system_call.s), and some misc functions ('verify_area').
 * Fork is rather simple, once you get the hang of it, but the memory
 * management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'
 */
#include "../include/errno.h"

#include "../include/linux/sched.h"
#include "../include/linux/kernel.h"
#include "../include/asm/segment.h"
#include "../include/asm/system.h"

extern void write_verify(unsigned long address);

long last_pid=0;

void verify_area(void * addr,int size)
{
	unsigned long start;

	start = (unsigned long) addr;
	size += start & 0xfff;
	start &= 0xfffff000;
	start += get_base(current->ldt[2]);
	while (size>0) {
		size -= 4096;
		write_verify(start);
		start += 4096;
	}
}

int copy_mem(int nr,struct task_struct * p)
{
	unsigned long old_data_base,new_data_base,data_limit;
	unsigned long old_code_base,new_code_base,code_limit;

    // 取当前进程局部描述符表中代码段描述符和数据段描述符项中的段限长（字节数）。
    // 0x0f 是代码段选择符；0x17 是数据段选择符。
	code_limit=get_limit(0x0f);
	data_limit=get_limit(0x17);
    // 取当前进程代码段和数据段在线性地址空间中的基地址。
	old_code_base = get_base(current->ldt[1]);
	old_data_base = get_base(current->ldt[2]);
    // 由于 Linux 0.11 内核还不支持代码和数据段分立的情况，因此这里需要检查代码段和数据段基址和限长是否都分别相同。
    // 否则内核显示出错信息，并停止运行。
	if (old_data_base != old_code_base)
		panic("We don't support separate I&D");
	if (data_limit < code_limit)
		panic("Bad data_limit");
    // 设置创建中的新进程在线性地址空间中的基地址等于（64MB* 其任务号），
    // 在Linux 0.11中，共有一张页目录表，该表是线性地址->物理地址的关键，它能表示多少空间，就表示线性地址有多少空间
    // 一个页目录表有1024个目录项(即有1024个页表)，每个页表有1024个页表项，每个页表项可以表示4K的物理空间
    // 1024个目录项 * 1024页表项 * 4K = 4G，在理论上一张页目录表 就可以表示4G的物理空间，也就是线性地址。
    // 每个任务的逻辑地址范围是64M, 4G / 64M = 64 这就是 为什么Linux 0.11 最大任务数 NR_TASKS = 64 的原因
    // 一张页表表示4M的空间范围， 因此需要4张页表(4个目录项)表示一个任务的地址。
    // 每个任务都是64M对齐的，所以任务对应的4个页目录项是连续的
	new_data_base = new_code_base = nr * TASK_SIZE;

	p->start_code = new_code_base;
    // 设置新进程局部描述符表中段描述符中的基地址。
	set_base(p->ldt[1],new_code_base);
	set_base(p->ldt[2],new_data_base);
    // 按着设置新进程的页目录表项和页表项， 即复制当前进程(父进程)的页目录表项和页表项。此时子进程共享父进程的内存页面。
    // 正常情况下 copy_page_tables()返回0，
	if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
        // 出错，则释放刚申请的页表项。
		printk("free_page_tables: from copy_mem\n");
		free_page_tables(new_data_base,data_limit);
		return -ENOMEM;
	}
	return 0;
}

/*
 *  Ok, this is the main fork-routine. It copies the system process
 * information (task[nr]) and sets up the necessary registers. It
 * also copies the data segment in it's entirety.
 */
/*
 * 复制进程。
 * 该函数的参数是进入系统调用中断处理过程（system_call.s） 开始，直到调用本系统调用处理
 * 过程（system_call.s 第208行）和调用本函数前时(system_call.s 第217 行）逐步压入栈的各寄存器的值。
 * 这些在 system_call.s 程序中逐步压入栈的值（参数）包括：
 * 1、CPU执行中断指令压入的用户栈地址 ss 和esp、标志寄存器 eflags 和返回地址 cs 和 eip
 * 2、第83--88 行在刚进入 system_call 时压入栈的段寄存器 ds、es、fs和 edx、 ecx、 ebx
 * 3、第94行调用 sys_call_table 中sys_fork 函数时压入栈的返回地址（用参数 none 表示）
 * 4、第212--216 行在调用 copy_process()之前压入栈的gs、esi、 edi、 ebp 和 eax (nr）值。其中参数 nr 是调用 find_empty_process() 分配的任务数组项号。
 *
*/

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx, long orig_eax,
        long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;

	p = (struct task_struct *) get_free_page();
	if (!p)
		return -EAGAIN;
	task[nr] = p;
	
	// NOTE!: the following statement now work with gcc 4.3.2 now, and you
	// must compile _THIS_ memcpy without no -O of gcc.#ifndef GCC4_3
	*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
	p->state = TASK_UNINTERRUPTIBLE;
	p->pid = last_pid;        //新进程号。也由 find empty_ process() 得到。
	p->counter = p->priority; //运行时间片值。
	p->signal = 0;    //信号位图置0
	p->alarm = 0;
	p->leader = 0;		/* process leadership doesn't inherit */
	p->utime = p->stime = 0;   //用户态时间和核心态运行时间。
	p->cutime = p->cstime = 0; //子进程用户态和核心态运行时间。
	p->start_time = jiffies;   //进程开始运行时间（当前时间滴答数）。
	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p; // 系统给任务结构p分配了1页新内存，所以(PAGE_SIZE +(long）p) 让 espO 正好指向该页顶端。
	p->tss.ss0 = 0x10;         // 内核态栈的段选择符（与内核数据段相同） ss0：esp0 用作程序在内核态执行时的栈。
	p->tss.eip = eip;          //指令代码指针。
	p->tss.eflags = eflags;    //标志寄存器。
	p->tss.eax = 0;            //新创建的进程，应该返回0。
	p->tss.ecx = ecx;
	p->tss.edx = edx;
	p->tss.ebx = ebx;
	p->tss.esp = esp;
	p->tss.ebp = ebp;
	p->tss.esi = esi;
	p->tss.edi = edi;
	p->tss.es = es & 0xffff;   //段寄存器仅16 位有效。
	p->tss.cs = cs & 0xffff;
	p->tss.ss = ss & 0xffff;
	p->tss.ds = ds & 0xffff;
	p->tss.fs = fs & 0xffff;
	p->tss.gs = gs & 0xffff;
    // 每个任务在 GDT 表中都有两个段描述符，一个是任务的 TSS 段描述符，另一个是任务的 LDT 表段描述符。
    // 把 GDT 中本任务 LDT 段描述符的选择符保存在本任务的TSS段中。
    // 当 CPU执行切换任务时，会自动从TSS中把 LDT 段描述符的选择符加载到ldtr 寄存器中。
	p->tss.ldt = _LDT(nr);
	p->tss.trace_bitmap = 0x80000000;
	if (last_task_used_math == current)
        __asm__("clts ; fnsave %0 ; frstor %0"::"m" (p->tss.i387));
    //复制进程页表。即在线性地址空间中设置新任务代码段和数据段描述符中的基址和限长，并复制页表。
	if (copy_mem(nr,p)) {
        // 如果出错（返回值不是0）则复位任务数组中相应项
		task[nr] = NULL;
        // 释放为该新任务分配的用于任务结构的内存页。
		free_page((long) p);
		return -EAGAIN;
	}
	for (i=0; i<NR_OPEN;i++)
		if ((f=p->filp[i]))
			f->f_count++;
	if (current->pwd)
		current->pwd->i_count++;
	if (current->root)
		current->root->i_count++;
	if (current->executable)
		current->executable->i_count++;
    if (current->library)
        current->library->i_count++;
    //n<<1 是因为TSS和LDT是同时出现，也就是说4，6，8，10 是TSS，5，7，9，11是LDT
	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
    p->p_pptr = current;
    p->p_cptr = 0;
    p->p_ysptr = 0;
    p->p_osptr = current->p_cptr;
    if (p->p_osptr)
        p->p_osptr->p_ysptr = p;
    current->p_cptr = p;
	p->state = TASK_RUNNING;	/* do this last, just in case */
	return last_pid;
}

int find_empty_process(void)
{
	int i;

	repeat:
		if ((++last_pid)<0) last_pid=1;
		for(i=0 ; i<NR_TASKS ; i++)
            if (task[i] && ((task[i]->pid == last_pid) ||
                            (task[i]->pgrp == last_pid)))
                goto repeat;
	for(i=1 ; i<NR_TASKS ; i++)
		if (!task[i])
			return i;
	return -EAGAIN;
}